﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.ComponentModel;
using System.Runtime.Serialization;


namespace XMas
{
    /// <summary>
    ///  Node that contains a animation, this node hasn't output, it is a finish node
    /// </summary>
    [Serializable()]
    public class AnimNode_Animation : AnimationNode
    {
        # region Files
        
        /// <summary>
        /// Mesh animation name 
        /// </summary>
        protected string   _AnimName;
        /// <summary>
        /// If we looping animation
        /// </summary>
        protected bool     _bLoop; 
        /// <summary>
        /// Animmation rate, can be less that 0
        /// </summary>
        protected float    _fRate;
        /// <summary>
        /// Current time of animmation
        /// </summary>
        protected TimeSpan _currentTimeValue;
        /// <summary>
        /// Current keyframe of animmation
        /// </summary>
        protected int      _iCurrentKeyframe;
        /// <summary>
        /// INTERNAL: Use to don't send twice the animend event
        /// </summary>
        protected bool     _bCanSendEndAnimEvent;
        /// <summary>
        /// When node become inactive we rewind the animation
        /// </summary>
        protected bool     _bRewindAtZeroWeight;
        /// <summary>
        /// We send an event when anim ends to animation system
        /// </summary>
        protected bool     _bSendAnimEndEvent;
        /// <summary>
        /// Skelenton data to fasten calcs
        /// </summary>
        protected CSkeletonData _skeletonData;
        /// <summary>
        /// Current animation clip 
        /// </summary>
        protected CSkeletonData.CAnimationData _animationClip;

        /// <summary>
        /// Number of version of this subnode, used in serialization
        /// </summary>
        /// <remarks>
        /// If you add a new value to serialization, you need to increment this float
        /// This is need to have compatibility with olders version of library
        /// <see cref="WriteXml(System.Xml.XmlWriter)"/>
        /// </remarks> 
        protected const float    _SerializationVersionChild = 1.0f;

        #endregion


        #region Delegates and Events

        /// <summary>
        /// Delegate for animation end event
        /// </summary>
        /// <param name="NodeName">Node name (Store in editor)</param>
        /// <param name="AnimName">Animation name</param>
        /// <param name="bLoop">If animation is looped</param>
        public delegate void OnAnimEndDelegate(string NodeName, string AnimName, bool bLoop);
        /// <summary>
        /// Animation end event, it raised when anim reached total time or 0 if rate is less than 0
        /// </summary>
        public event OnAnimEndDelegate OnAnimEndEvent;

        #endregion


        #region Properties

        /// <summary>
        /// Atribute for _AnimName
        /// <see cref="AnimNode_Animation._AnimName"/>
        /// </summary>
        [TypeConverter(typeof(AnimationNameConverter)),
        CategoryAttribute("Animation"), DescriptionAttribute("Animation Name")]        
        public string AnimName
        {
            get { return _AnimName; }
            set 
            { 
                _AnimName = value;
                if (_AnimName.Length > 0)
                    _strName = value;

                SetNewClip();
            }
        }


        /// <summary>
        /// Atribute for _bLoop
        /// <see cref="AnimNode_Animation._bLoop"/>
        /// </summary>
        [CategoryAttribute("Animation"), DescriptionAttribute("Loop Anim")]        
        public bool Loop
        {
            get { return _bLoop; }
            set { _bLoop = value; }
        }

        /// <summary>
        /// Atribute for _fRate
        /// <see cref="AnimNode_Animation._fRate"/>
        /// </summary>
        [CategoryAttribute("Animation"), DescriptionAttribute("Rate")]
        public float Rate
        {
            get { return _fRate; }
            set { _fRate = value; }
        }

        /// <summary>
        /// Atribute for _bRewindAtZeroWeight
        /// <see cref="AnimNode_Animation._bRewindAtZeroWeight"/>
        /// </summary>
        [CategoryAttribute("Animation"), DescriptionAttribute("Rewind At Zero Weight")]
        public bool RewindAtZeroWeight
        {
            get { return _bRewindAtZeroWeight; }
            set { _bRewindAtZeroWeight = value; }
        }

        /// <summary>
        /// Atribute for _bSendAnimEndEvent
        /// <see cref="AnimNode_Animation._bSendAnimEndEvent"/>
        /// </summary>
        [CategoryAttribute("Animation"), DescriptionAttribute("Send Anim End Event")]
        public bool SendAnimEndEvent
        {
            get { return _bSendAnimEndEvent; }
            set { _bSendAnimEndEvent = value; }
        }

        
        // No browsables

        /// <summary>
        /// Atribute for ReflexionProperty
        /// <see cref="AnimNode_Animation.ReflexionProperty"/>
        /// </summary>
        [Browsable(false)]
        public override string ReflexionProperty
        {
            get { return base.ReflexionProperty; }
            set { base.ReflexionProperty = value; }
        }

        /// <summary>
        /// Atribute for _skeletonData
        /// <see cref="AnimNode_Animation._skeletonData"/>
        /// </summary>
        [Browsable(false)]
        public CSkeletonData SkeletonData
        {
            get { return _skeletonData; }
            set 
            { 
                _skeletonData = value;
                SetNewClip();               
            }            
        }

        /// <summary>
        /// Atribute for _currentTimeValue
        /// <see cref="AnimNode_Animation._currentTimeValue"/>
        /// </summary>
        [Browsable(false)]
        public TimeSpan CurrentTime
        {
            get { return _currentTimeValue; }            
        }

        /// <summary>
        /// Atribute for _iCurrentKeyframe
        /// <see cref="AnimNode_Animation._iCurrentKeyframe"/>
        /// </summary>
        [Browsable(false)]
        public int CurrentKeyframe
        {
            get { return _iCurrentKeyframe; }            
        }

        /// <summary>
        /// Atribute for _animationClip
        /// <see cref="AnimNode_Animation._animationClip"/>
        /// </summary>
        [Browsable(false)]
        public CSkeletonData.CAnimationData AnimationClip
        {
            get { return _animationClip; }
        }


        #endregion


        # region Protected Members

        /// <summary>
        /// Initialization of node, set initial values
        /// </summary>
        /// <param name="ID">Node ID</param>
        protected override void Initialize(System.Guid ID)
        {
            base.Initialize(ID);
            _strName = "Animation";
            _bFixedChildren = true;
            _fEditorValue = 0.0f;
            _AnimName = "";
            _CategoryName = "";
            _currentTimeValue = TimeSpan.Zero;
            _iCurrentKeyframe = 0;
            _animationClip = null;
            _skeletonData = null;
            _bLoop = true;
            _bRewindAtZeroWeight = true;
            _bSendAnimEndEvent = false;
            _bCanSendEndAnimEvent = true;
            _fRate = 1.0f;
        }

        /// <summary>
        /// Internal get current value, first try to use reflexion. If no reflexion found use editor value
        /// Override with caution
        /// </summary>
        /// <returns>Value from 0 to 1</returns>
        protected override float GetValue()
        {
            // Fixed
            return 1.0f;
        }

        /// <summary>
        /// Internal function to get the animation clip and reset time
        /// </summary>
        protected void SetNewClip()
        {
            CSkeletonData.CAnimationData newClip = null;


            if (_skeletonData != null && _skeletonData.list_animations.ContainsKey(_AnimName))
                newClip = _skeletonData.list_animations[_AnimName];

            if (newClip != _animationClip || newClip == null)
            {
                _currentTimeValue = TimeSpan.Zero;
                _iCurrentKeyframe = 0;

                if (_fRate < 0 && newClip != null)
                {
                    _iCurrentKeyframe = newClip.list_frames.Count - 1;
                    _currentTimeValue = TimeSpan.FromSeconds((double)newClip.frame_rate * _iCurrentKeyframe);
                }
            }
            _animationClip = newClip;
            _bCanSendEndAnimEvent = true;
        }

        #endregion


        # region Public Events

        /// <summary>
        /// Called when node become inactive, 0 weith
        /// </summary>
        public override void OnBecomeInactive()
        {
            // Rewind if needs
            if (_bRewindAtZeroWeight)
            {
                if (_fRate >= 0)
                {
                    _currentTimeValue = TimeSpan.Zero;
                    _iCurrentKeyframe = 0;
                }
                else
                {
                    _iCurrentKeyframe = _animationClip.list_frames.Count - 1;
                    _currentTimeValue = TimeSpan.FromSeconds((double)_animationClip.frame_rate * _iCurrentKeyframe);
                }
                _bCanSendEndAnimEvent = true;
            }
        }

        #endregion


        # region Public Members

        /// <summary>
        /// Reset current time
        /// </summary>
        public void ResetTime()
        {
            _iCurrentKeyframe = 0;
            _currentTimeValue = TimeSpan.Zero;
        }

        /// <summary>
        /// Used by system when the node need to update his weight
        /// </summary>
        /// <param name="DeltaSeconds">Elapsed time</param>
        public override void UpdateWeights(double DeltaSeconds)
        {
            _fCurrentValue = GetValue();
            //_fEditorValue = _fCurrentValue;
        }

        /// <summary>
        /// Used by system after the node update his weight
        /// </summary>
        /// <param name="elapsedTime">Elapsed</param>
        public override void PostUpdateWeights(TimeSpan elapsedTime)
        {
            // Post Node tick with real weights
            // Advance animation (only weigtheds)
            if (_animationClip != null && IncomeWeigth > 0)
            {
                _currentTimeValue += TimeSpan.FromSeconds((float)elapsedTime.TotalSeconds * _fRate);
                 float fFrameTime  = (float)_currentTimeValue.TotalSeconds / _animationClip.frame_rate;
                _iCurrentKeyframe = (int)fFrameTime;
                
                // Loop 
                if (_iCurrentKeyframe >= _animationClip.list_frames.Count || _currentTimeValue.TotalSeconds < 0)
                {
                    // Send end animation event
                    if (_bCanSendEndAnimEvent)
                    {
                        // Send to parent first
                        foreach (AnimationNode animNode in _parentNodes)
                            animNode.OnChildAnimEnd(this);
                        
                        // Send to delegate
                        if (_bSendAnimEndEvent && OnAnimEndEvent != null)
                            OnAnimEndEvent(Name, AnimName, Loop);
                    }

                    if (_bLoop)
                    {
                        if (_currentTimeValue.TotalSeconds < 0)
                        {
                            float fRestTime = (float)_currentTimeValue.TotalSeconds;
                            fRestTime = (_animationClip.frame_rate * (_animationClip.list_frames.Count - 1)) + fRestTime;
                            _currentTimeValue = TimeSpan.FromSeconds((double)fRestTime);
                            _iCurrentKeyframe = _animationClip.list_frames.Count - 1 + _iCurrentKeyframe;
                        }
                        else
                        {
                            float fRestTime = (float)_currentTimeValue.TotalSeconds - (_animationClip.frame_rate * _iCurrentKeyframe);
                            _currentTimeValue = TimeSpan.FromSeconds((double)fRestTime);
                            _iCurrentKeyframe = _iCurrentKeyframe - _animationClip.list_frames.Count;
                        }
                    }
                    else
                    {
                        if (_currentTimeValue.TotalSeconds < 0)
                        {
                            _iCurrentKeyframe = 0;
                            _currentTimeValue = TimeSpan.Zero;
                        }
                        else
                        {
                            _iCurrentKeyframe = _animationClip.list_frames.Count - 1;
                            _currentTimeValue = TimeSpan.FromSeconds((double)_animationClip.frame_rate * (_iCurrentKeyframe + 1));
                        }
                        _bCanSendEndAnimEvent = false;
                    }
                }
                else
                    _bCanSendEndAnimEvent = true;

                // Calculate percent for editor
                _fEditorValue = ((float)_iCurrentKeyframe / (float)_animationClip.list_frames.Count);
            }
        }

        /// <summary>
        /// EDITOR EVENT: Called by editor when slider move
        /// </summary>
        /// <param name="iNewValue">Value is between 0 and 100</param>
        public override void HandleSliderMove(int iNewValue)
        {
        }

        #endregion
        

        #region Load/Save XML

        /// <summary>
        /// Function to write in XML format, if you overwrite you must call base class first
        /// </summary>
        /// <remarks>
        /// Be carefully with the save order, it must be the same that read order!!
        /// </remarks>
        /// <param name="writer">Writer to store data</param>
        public override void WriteXml(XmlWriter writer)
        {
            base.WriteXml(writer);

            writer.WriteElementString("SerializationVersionChild", _SerializationVersionChild.ToString(new System.Globalization.CultureInfo("en-US")));
            writer.WriteElementString("AnimName", _AnimName);
            writer.WriteElementString("Loop", _bLoop ? "1" : "0");
            writer.WriteElementString("RewindAtZeroWeight", _bRewindAtZeroWeight ? "1" : "0");
            writer.WriteElementString("SendAnimEndEvent", _bSendAnimEndEvent ? "1" : "0");
            writer.WriteElementString("Rate", _fRate.ToString(new System.Globalization.CultureInfo("en-US")));
        }

        /// <summary>
        /// Function to read xml format, if you overwrite you must call base class first
        /// </summary>
        /// <remarks>
        /// Be carefully with the read order, it must be the same that save order!!
        /// </remarks>
        /// <param name="reader">Reader to retrieve data</param>
        public override void ReadXml(XmlReader reader)
        {
            base.ReadXml(reader);
            
            float SerializationVersionChild = reader.ReadElementContentAsFloat("SerializationVersionChild", reader.NamespaceURI);
            _AnimName = reader.ReadElementContentAsString("AnimName", reader.NamespaceURI);
            _bLoop = reader.ReadElementContentAsBoolean("Loop", reader.NamespaceURI);
            _bRewindAtZeroWeight = reader.ReadElementContentAsBoolean("RewindAtZeroWeight", reader.NamespaceURI);
            _bSendAnimEndEvent = reader.ReadElementContentAsBoolean("SendAnimEndEvent", reader.NamespaceURI);
            _fRate = reader.ReadElementContentAsFloat("Rate", reader.NamespaceURI);
        }

        #endregion

        /// <summary>
        /// Constructor
        /// </summary>
        public AnimNode_Animation() 
        {          
        }
    }
}
